package com.github.glomadrian.materialanimatedswitch;

import com.github.glomadrian.materialanimatedswitch.observer.BallFinishObservable;
import com.github.glomadrian.materialanimatedswitch.observer.BallMoveObservable;
import com.github.glomadrian.materialanimatedswitch.painter.*;
import com.github.glomadrian.materialanimatedswitch.utils.AttrSetUtils;
import com.github.glomadrian.materialanimatedswitch.utils.Utils;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.render.Canvas;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.multimodalinput.event.TouchEvent;

import java.util.Observable;
import java.util.Observer;

public class MaterialAnimatedSwitch extends Component
        implements Component.EstimateSizeListener,
        Component.DrawTask,
        Component.TouchEventListener {
    private int margin;
    private BasePainter basePainter;
    private Painter ballPainter;
    private BallShadowPainter ballShadowPainter;
    private MaterialAnimatedSwitchState actualState;
    private IconPressPainter iconPressPainter;
    private IconReleasePainter iconReleasePainter;
    private int baseColorRelease = Color.getIntColor("#3061BE");
    private int baseColorPress = Color.getIntColor("#D7E7FF");
    private int ballColorRelease = Color.getIntColor("#5992FB");
    private int ballColorPress = Color.getIntColor("#FFFFFF");
    private int ballShadowColor = Color.getIntColor("#99000000");
    private PixelMapElement releaseIcon;
    private PixelMapElement pressIcon;
    private BallFinishObservable ballFinishObservable;
    private BallMoveObservable ballMoveObservable;
    private boolean isClickable = true;
    private boolean isRefreshable = false;
    private boolean isToggle = false;
    private CancelTask cancelTask = new CancelTask();
    private OnCheckedChangeListener onCheckedChangeListener;
    private EventHandler mainHandler = new EventHandler(EventRunner.getMainEventRunner());

    public MaterialAnimatedSwitch(Context context) {
        super(context);
        init();
    }

    public MaterialAnimatedSwitch(Context context, AttrSet attrSet) {
        super(context, attrSet);
        init(attrSet);
    }

    public MaterialAnimatedSwitch(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(attrSet);
    }

    private void init() {
        margin = Utils.vp2px(getContext(), 14);
        initObservables();
        initPainters();
        actualState = MaterialAnimatedSwitchState.INIT;
        setState(actualState);

        setEstimateSizeListener(this);
        addDrawTask(this);
        setTouchEventListener(this);
    }

    private void initPainters() {
        basePainter = new BasePainter(baseColorRelease, baseColorPress, margin, ballMoveObservable);
        ballPainter = new BallPainter(ballColorRelease, ballColorPress, margin, ballFinishObservable,
                ballMoveObservable, getContext());

        ballShadowPainter =
                new BallShadowPainter(ballShadowColor, ballShadowColor, margin, ballShadowColor,
                        ballFinishObservable, ballMoveObservable, getContext());
        iconPressPainter =
                new IconPressPainter(getContext(), pressIcon.getPixelMap(),
                        ballFinishObservable, ballMoveObservable,
                        margin);
        iconReleasePainter =
                new IconReleasePainter(getContext(), releaseIcon.getPixelMap(),
                        ballFinishObservable, margin);
    }

    private void init(AttrSet attrs) {
        initAttributes(attrs);
        init();
    }

    private void initAttributes(AttrSet attrs) {
        baseColorRelease = AttrSetUtils.optColor(attrs, "base_release_color", baseColorRelease);
        baseColorPress = AttrSetUtils.optColor(attrs, "base_press_color", baseColorPress);
        ballColorRelease = AttrSetUtils.optColor(attrs, "ball_release_color", ballColorRelease);
        ballColorPress = AttrSetUtils.optColor(attrs, "ball_press_color", ballColorPress);
        pressIcon = (PixelMapElement) AttrSetUtils.optElement(getContext(),
                attrs, "icon_press",
                ResourceTable.Media_tack_save_button_32_blue);
        releaseIcon = (PixelMapElement) AttrSetUtils.optElement(getContext(),
                attrs, "icon_release",
                ResourceTable.Media_tack_save_button_32_white);
    }

    private void initObservables() {
        ballFinishObservable = new BallFinishObservable();
        ballMoveObservable = new BallMoveObservable();
        ballFinishObservable.addObserver(new BallStateObserver());
    }

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int width = Utils.vp2px(getContext(), 45);
        int height = Utils.vp2px(getContext(), 28);
        setEstimatedSize(EstimateSpec.getSizeWithMode(width, EstimateSpec.PRECISE),
                EstimateSpec.getSizeWithMode(height, EstimateSpec.PRECISE));
        basePainter.onSizeChanged(height, width);
        ballShadowPainter.onSizeChanged(height, width);
        ballPainter.onSizeChanged(height, width);
        iconPressPainter.onSizeChanged(height, width);
        iconReleasePainter.onSizeChanged(height, width);
        return true;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        basePainter.draw(canvas);
        ballShadowPainter.draw(canvas);
        ballPainter.draw(canvas);
        iconPressPainter.draw(canvas);
        iconReleasePainter.draw(canvas);
        if (isRefreshable) {
            getContext().getUITaskDispatcher().asyncDispatch(new Runnable() {
                @Override
                public void run() {
                    invalidate();
                }
            });
        }
    }

    private void setState(MaterialAnimatedSwitchState materialAnimatedSwitchState) {
        basePainter.setState(materialAnimatedSwitchState);
        ballPainter.setState(materialAnimatedSwitchState);
        ballShadowPainter.setState(materialAnimatedSwitchState);
        iconPressPainter.setState(materialAnimatedSwitchState);
        iconReleasePainter.setState(materialAnimatedSwitchState);
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:

                if (isClickable) {
                    isToggle = false;
                    doActionDown();
                }
                return true;
            default:
                return false;
        }
    }

    private void doActionDown() {
        isRefreshable = true;

        mainHandler.removeTask(cancelTask);
        mainHandler.postTask(cancelTask, 1000);

        if (actualState.equals(MaterialAnimatedSwitchState.RELEASE) || actualState.equals(
                MaterialAnimatedSwitchState.INIT) || actualState == null) {
            actualState = MaterialAnimatedSwitchState.PRESS;
            setState(actualState);
        } else {
            actualState = MaterialAnimatedSwitchState.RELEASE;
            setState(actualState);
        }

        getContext().getUITaskDispatcher().asyncDispatch(new Runnable() {
            @Override
            public void run() {
                invalidate();
            }
        });
    }

    public boolean isChecked() {
        return actualState.equals(MaterialAnimatedSwitchState.PRESS);
    }

    public void toggle() {
        if (isClickable) {
            isToggle = true;
            doActionDown();
        }
    }

    public void setOnCheckedChangeListener(OnCheckedChangeListener onCheckedChangeListener) {
        this.onCheckedChangeListener = onCheckedChangeListener;
    }

    public interface OnCheckedChangeListener {
        void onCheckedChanged(boolean isChecked);
    }

    /**
     * Avoid click when ball is still in movement
     * Call listener when state is updated
     */
    private class BallStateObserver implements Observer {
        @Override
        public void update(Observable observable, Object data) {
            BallFinishObservable ballFinishObservable = (BallFinishObservable) observable;
            isClickable = !ballFinishObservable.getState().equals(BallFinishObservable.BallState.MOVE);

            if (ballFinishObservable.getState().equals(BallFinishObservable.BallState.PRESS)) {
                if (onCheckedChangeListener != null && !isToggle) {
                    onCheckedChangeListener.onCheckedChanged(true);
                }
            } else if (ballFinishObservable.getState().equals(BallFinishObservable.BallState.RELEASE)) {
                if (onCheckedChangeListener != null && !isToggle) {
                    onCheckedChangeListener.onCheckedChanged(false);
                }
            }
        }
    }

    private class CancelTask implements Runnable {
        @Override
        public void run() {
            isRefreshable = false;
        }
    }
}
